// 最大内存单元数 65536 * 2
let MEMORY_MAX = 1 << 16;
// PC初始值
let PC_START = 0x3000;

let STEPS_PER_FRAME = 100000;
let EXIT_SUCCESS = true;
let EXIT_INPUT_TOO_LARGE = -1;
let EXIT_OPCODE_NOT_IMPLEMENTED = -2;
let EXIT_RETRY_AFTER_INTERRUPT = -3;

// 寄存器
let R_R0 = 0;
let R_R1 = 1;
let R_R2 = 2;
let R_R3 = 3;
let R_R4 = 4;
let R_R5 = 5;
let R_R6 = 6;
let R_R7 = 7;
let R_PC = 8;
let R_COND = 9;
let R_COUNT = 10;

// 标志寄存器标志位
let FL_POS = 0b001;
let FL_ZRO = 0b010;
let FL_NEG = 0b100;

// 内存映射寄存器
let MR_KBSR = 0xFE00;
let MR_KBDR = 0xFE02;
var ADDR_DSR  = 0xfe04;
var ADDR_DDR  = 0xfe06;
var ADDR_MCR  = 0xfffe;

// 操作码
let OP_BR = 0b0000;
let OP_ADD = 0b0001;
let OP_LD = 0b0010;
let OP_ST = 0b0011;
let OP_JSR = 0b0100;
let OP_AND = 0b0101;
let OP_LDR = 0b0110;
let OP_STR = 0b0111;
let OP_RTI = 0b1000;
let OP_NOT = 0b1001;
let OP_LDI = 0b1010;
let OP_STI = 0b1011;
let OP_JMP = 0b1100;
let OP_RES = 0b1101;
let OP_LEA = 0b1110;
let OP_TRAP = 0b1111;

// 操作码集合
let OPSET_R0 = 0x4EEE;
let OPSET_R1 = 0x12F2;
let OPSET_R2 = 0x0022;
let OPSET_BASE = 0x00C0;
let OPSET_PC = 0x4C0D;

// 中断信号
let TRAP_GETC = 0x20;
let TRAP_OUT = 0x21;
let TRAP_PUTS = 0x22;
let TRAP_IN = 0x23;
let TRAP_PUTSP = 0x24;
let TRAP_HALT = 0x25;

let STATUS_BIT = 1 << 15;

// Lc3Go中这个方法正确，js中这个计算错误，signExtend(0x1fd,9) 应该为-3。
// 根本原因是0xFFFF << bitCount,在js中，结果为0xFFFF00，而非0xFF00。

// function signExtend(x, bitCount) {
//     if (x >> (bitCount - 1) & 1) {
//         x |= (0xFFFF << bitCount);
//     }
//     return x;
// }


function signExtend(val, n) {
    var m = 1 << (n - 1);
    val &= (1 << n) - 1;
    return (val ^ m) - m;
}

function swap16(x) {
    return (x << 8) | (x >> 8);
}

function VirtualMachine() {
    if (!(this instanceof VirtualMachine)) {
        return new VirtualMachine();
    }
    // 开启调试模式支持单步运行
    this.debugMode = false;
    // 初始化虚拟机
    this.reset();
}

VirtualMachine.prototype.reset = function() {
    this.mem = new Uint16Array(MEMORY_MAX);
    this.reg = new Uint16Array(R_COUNT);
    this.running = true;
    this.reg[R_PC] = PC_START;
    this.reg[R_COND] = FL_ZRO;
    this.mem[ADDR_MCR] = STATUS_BIT;
    this.retryAfterInterrupt = false;
    if (this.animationFrameHandle) {
        window.cancelAnimationFrame(this.animationFrameHandle);
        this.animationFrameHandle = undefined;
    }
}

VirtualMachine.prototype.updateFlags = function(r) {
    if (this.reg[r] == 0) {
        this.reg[R_COND] = FL_ZRO;
    } else if (this.reg[r] >> 15 > 0) {
        this.reg[R_COND] = FL_NEG;
    } else {
        this.reg[R_COND] = FL_POS;
    }
}

VirtualMachine.prototype.setDebugMode = function(debugMode) {
    this.debugMode = debugMode;
}

VirtualMachine.prototype.write = function(address, val) {
    if (address == MR_KBSR || address == MR_KBDR || address == ADDR_DSR) {
        return;
    } else if (address == ADDR_DDR) {
        this.putChar(val);
    } else {
        this.mem[address] = val;
    }
}

VirtualMachine.prototype.read = function(address) {
    if (address == MR_KBSR) {
        if (this.hasChar()) {
            // 标志位，表示已读取到字符，改标志位为0时，cpu会不断轮询检测
            this.mem[MR_KBSR] = 1 << 15
            this.mem[MR_KBDR] = this.getChar();
        } else {
            return 0;
        }
    } else if (address == ADDR_DSR) {
        return STATUS_BIT;
    } else if (address == ADDR_DDR) {
        return 0;
    }
    return this.mem[address];
}

VirtualMachine.prototype.run = function() {
    this.animationFrameHandle = window.requestAnimationFrame(() => this.step());
}

VirtualMachine.prototype.interrupt = function() {
    if (this.retryAfterInterrupt) {
        this.retryAfterInterrupt = false;
        this.run();
    }
};

VirtualMachine.prototype.step = function() {
    if (!this.running) {
        return;
    }
    for (let i = 0; i < STEPS_PER_FRAME; i++) {
        if (!(this.read(ADDR_MCR) & STATUS_BIT)) {
            // halted
            return;
        }
        let instr = this.mem[this.reg[R_PC]++];
        let res = this.ins(instr);
        if (res == EXIT_RETRY_AFTER_INTERRUPT) {
            // PC指针自减，在目前的运行中没感觉到这个有什么必要性，减不减都正常运行。可能时因为目前触发的该行为都是监听键盘输入的轮询。
            this.reg[R_PC]--;

            this.retryAfterInterrupt = true;
            return;
        } else if (res != EXIT_SUCCESS) {
            return;
        }
    }
    if (!this.debugMode) {
        this.run();
    }
}

VirtualMachine.prototype.ins = function(instr) {
    let r0,r1,r2,imm5,immFlag;
    let pcPlusOff,basePlusOff;
    // 获取指令码
    let op = instr >> 12;
    let opbit = 1 << op;

    if (OPSET_R0 & opbit) {
        r0 = (instr >> 9) & 0x7;
    }
    if (OPSET_R1 & opbit) {
        r1 = (instr >> 6) & 0x7;
    }
    if (OPSET_R2 & opbit) {
        immFlag = (instr >> 5) & 0x1;
        if (immFlag) {
            imm5 = signExtend(instr & 0x1F, 5);
        } else {
            r2 = instr & 0x7;
        }
    }
    if (OPSET_BASE & opbit) {
        // Base + offset
        basePlusOff = this.reg[r1] + signExtend(instr & 0x3F, 6);
    }
    if (OPSET_PC & opbit) {
        // Indirect address PC + offset
        pcPlusOff = this.reg[R_PC] + signExtend(instr & 0x1FF, 9);
    }
    if (op == OP_BR) {
        // BR
        cond = (instr >> 9) & 0x7;
        if (cond & this.reg[R_COND]) {
            this.reg[R_PC] = pcPlusOff;
        }
    } else if (op == OP_ADD) {
        if (immFlag) {
            this.reg[r0] = this.reg[r1] + imm5;
        } else {
            this.reg[r0] = this.reg[r1] + this.reg[r2];
        }
    } else if (op == OP_AND) {
        if (immFlag) {
            this.reg[r0] = this.reg[r1] & imm5;
        } else {
            this.reg[r0] = this.reg[r1] & this.reg[r2];
        }
    } else if (op == OP_NOT) {
        this.reg[r0] = ~this.reg[r1];
    } else if (op == OP_JMP) {
        this.reg[R_PC] = this.reg[r1];
    } else if (op == OP_JSR) {
        longFlag = (instr >> 11) & 1;
        this.reg[R_R7] = this.reg[R_PC];
        if (longFlag) {
            pcPlusOff = this.reg[R_PC] + signExtend(instr & 0x7FF, 11);
            this.reg[R_PC] = pcPlusOff;
        } else {
            this.reg[R_PC] = this.reg[r1];
        }
    } else if (op == OP_LD) {
        this.reg[r0] = this.read(pcPlusOff);
    } else if (op == OP_LDI) {
        this.reg[r0] = this.read(this.read(pcPlusOff));
    } else if (op == OP_LDR) {
        this.reg[r0] = this.read(basePlusOff);
    } else if (op == OP_LEA) {
        this.reg[r0] = pcPlusOff;
    } else if (op == OP_ST) {
        this.write(pcPlusOff, this.reg[r0]);
    } else if (op == OP_STI) {
        this.write(this.read(pcPlusOff), this.reg[r0]);
    } else if (op == OP_STR) {
        this.write(basePlusOff, this.reg[r0]);
    } else if (op == OP_RTI) {
        this.updateFlags(r0);
    } else if (op == OP_TRAP) {
        this.reg[R_R7] = this.reg[R_PC];
        switch (instr & 0xFF) {
            case TRAP_GETC:
                if (!this.hasChar()) {
                    // 没有字符，等输入字符后再执行
                    return EXIT_RETRY_AFTER_INTERRUPT;
                }
                this.reg[R_R0] = this.getChar();
                this.updateFlags(R_R0)
                break
            case TRAP_OUT:
                this.putChar(this.reg[R_R0]);
                break
            case TRAP_PUTS:
                for (let i = this.reg[R_R0]; this.mem[i]; i++) {
                    this.putChar(this.mem[i]);
                }
                break
            case TRAP_IN:
                // this.putStr("Enter a character:");
                if (!this.hasChar()) {
                    // 没有字符，等输入字符后再执行
                    return EXIT_RETRY_AFTER_INTERRUPT;
                }
                let ch = this.getChar();
                this.putChar(ch);
                this.reg[R_R0] = ch
                this.updateFlags(R_R0)
                break
            case TRAP_PUTSP:
                for (let i = this.reg[R_R0]; this.mem[i]; i++) {
                    this.putChar(this.mem[i] & 0xFF);
                    let ch = this.mem[i] >> 8;
                    if (ch) {
                        this.putChar(ch);
                    }
                }
                break
            case TRAP_HALT:
                this.putStr("HALT");
                this.running = false
                break
        }
    } else if (op == OP_RES) {
        return EXIT_OPCODE_NOT_IMPLEMENTED;
    }

    if (0x4666 & opbit) {
        this.updateFlags(r0)
    }
    return EXIT_SUCCESS;
}

VirtualMachine.prototype.stepAfter = function() {

}

VirtualMachine.prototype.putStr = function(str) {
    if (str) {
        for (let v of str) this.putChar(v);
    }
};

VirtualMachine.prototype.putChar = function(val) {
    console.log(val); // eslint-disable-line no-console
};

VirtualMachine.prototype.hasChar = function() {
    return false;
};

VirtualMachine.prototype.getChar = function() {
    return 0;
};

VirtualMachine.prototype.loadRom = function(rom) {
    let arr16 = new Uint16Array(rom.buffer);

    let orign = swap16(arr16[0]);
    let length = arr16.length = 1;
    if (orign + length > MEMORY_MAX) {
        return EXIT_INPUT_TOO_LARGE;
    }
    for (let i = 1; i < arr16.length; i++) {
        this.mem[orign + i - 1] = swap16(arr16[i]);
    }
    return EXIT_SUCCESS;
}


